home *** CD-ROM | disk | FTP | other *** search
/ Mac Format 1994 October / Macformat17.cdr / Shareware City / Developers / xlispmac / src / XLisp Internals < prev    next >
Text File  |  1994-05-28  |  42KB  |  989 lines

  1. I've just finished reading the xlisp 2.1 source code for the first
  2. time.  The tutorial and reference material included with the winterp
  3. distribution are well done, but I would have liked an overview of the
  4. interpreter internals.  Here's a first cut at such a document.
  5. Comments welcome...
  6.  
  7. Jeff Prothero
  8.  
  9. I've modified Jeff's overview to match current XLISP-PLUS 2.1g
  10. practice, more thoroughly describe the operation of contexts, and 
  11. describe initialization and save/restore.
  12.  
  13. Tom Almy
  14.  
  15.  
  16. ---------------------------cut here-------------------------------
  17.  
  18. 90Nov13 jsp@milton.u.washington.edu       Public Domain.
  19.  
  20.                    +---------------------+
  21.                    | xlisp 2.1 internals |
  22.                    +---------------------+
  23.  
  24.  
  25.  Who should read this?
  26.  ---------------------
  27.  
  28. Anyone poking through the C implementation of xlisp for the first
  29. time.  This is intended to provide a rough roadmap of the global
  30. xlisp structures and algorithms.  If you just want to write lisp code
  31. in xlisp, you don't need to read this file -- go read cldoc.txt.  If
  32. you want to tinker with the xlisp implementation code, you should
  33. *still* read that before reading this.  The following isn't intended
  34. to be exhaustively precise -- that's what the source code is for!  It
  35. is intended only to give you sufficient orientation give you a
  36. fighting chance to understand the code the first time through,
  37. instead of the third time.
  38.  
  39. At the bottom of the file you'll find an example of how to add new
  40. primitive functions to xlisp.
  41.  
  42.  
  43.  
  44.  General note on MS-DOS Memory Models
  45.  ------------------------------------
  46.  
  47. If you are not using MS-DOS ignore all the "FAR" and "NEAR" keywords.
  48. Also ignore MEDMEM, and most uppercase macros default to the lowercase
  49. library functions.
  50.  
  51. For MS-DOS, originally XLISP used Large memory model. However in
  52. order to allow for a clean MS Windows port (never completed :-() and
  53. to improve performance, the code was overhauled to use Medium memory
  54. model. Every xlisp object is is allocated from FAR memory, so
  55. pointers need to be cast to FAR. Local variables, and statically
  56. allocated global variables are NEAR, however. Since FAR strings cannot be
  57. handled by Medium model library string routines, they must be moded
  58. into a NEAR buffer first.
  59.  
  60.  
  61.  Other added confusions
  62.  ______________________
  63.  
  64.  
  65. There are two separate sets of memory allocation/garbage collection
  66. routines. One pair, xldmem/xlimage, was the traditional xlisp
  67. allocator. The second pair, dldmem/dlimage, additionally manages
  68. arrays and strings. In the original version, malloc and free are
  69. used, which can cause memory fragmentation problems. The dl pair 
  70. compresses array memory segments to eliminate fragmentation.
  71.  
  72. There are many compilation options. Thank goodness many more have
  73. been eliminated.
  74.  
  75.  
  76.  What is an LVAL?
  77.  ----------------
  78.  
  79. An "LVAL" is the C type for a generic pointer to an xlisp
  80. garbage-collectable something.  (Cons cell, object, string, closure,
  81. symbol, vector, whatever.)  Virtually every variable in the
  82. interpreter is an LVAL.  Cons cells contain two LVAL slots,
  83. symbols contains four LVAL slots, etc.
  84.  
  85.  
  86.  
  87.  What is the obarray?
  88.  --------------------
  89.  
  90. The obarray is the xlisp symbol table.  More precisely, it is is a
  91. hashtable mapping ascii strings (symbol names) to symbols.  ("obarray"
  92. is a misnomer, since it contains only xlisp SYMBOLs, and in particular
  93. contains no xlisp OBJECTs.)  It is used when converting lisp
  94. expressions from text to internal form. Since it is a root for the
  95. garbage collector, it also serves to distinguish permanent
  96. global-variable symbols from other symbols -- you can permanently
  97. protect a symbol from the garbage collector by entering it into the
  98. obarray.  This is called "interning" the symbol.  The obarray is
  99. called "obarray" in C and "*OBARRAY*" in xlisp.
  100.  
  101. When the package facility is compiled, *OBARRAY* no longer exists,
  102. and obarray is a list of packages. A package is a structure (an
  103. array) of six elements. The first is an obarray for the internal
  104. symbols in the package, the second is an obarry for the external
  105. symbols, the third is a list of shadowing symbols, the fourth is a
  106. list of packages this package uses, the fifth is a list of packages
  107. which use this package, and the sixth is a list of the package's
  108. names with all but the first being the nicknames. In addition,
  109. symbols have an additional attribute in their structure which is the
  110. home package of the symbol.
  111.  
  112.  
  113.  The Interpreter Stacks
  114.  ----------------------
  115.  
  116. xlisp uses two stacks, an "evaluation stack" and an "argument stack".
  117. Both are roots for the garbage collector.  The evaluation stack is
  118. largely private to the interpreter and protects internal values from
  119. garbage collection, while the argument stack holds the conventional
  120. user-visible stackframes.
  121.  
  122.  
  123. The evaluation stack is an EDEPTH-long array of "LVAL" statically
  124. allocated.  It grows zeroward.
  125.  
  126. xlstkbase points to the zero-near end of the evaluation stack.
  127.  
  128. xlstktop points to the zero-far end of the evaluation stack: the
  129. occupied part of the stack lies between xlstack and xlstktop.  NOTE
  130. that xlstktop is *NOT* the top of the stack in the conventional sense
  131. of indicating the most recent entry on the stack: xlstktop is a static
  132. bounds pointer which never changes.
  133.  
  134. xlstack starts at the zero-far end of the evaluation stack.  *xlstack
  135. is the most recent LVAL on the stack.  The garbage collector MARKs
  136. everything reachable from the evaluation stack (among other things),
  137. so we frequently push things on this stack while C code is
  138. manipulating them. (Via xlsave(), xlprotect(), xlsave1(), xlprot1().)
  139.  
  140.  
  141. The argument stack is an ADEPTH-long array of "LVAL".  It also grows
  142. zeroward.  The evaluator pushes arguments on the argument stack at the
  143. start of a function call (form evaluation).  Built-in functions
  144. usually eat them directly off the stack.  For user-lisp functions
  145. xleval.c:evfun() pops them off the stack and binds them to the
  146. appropriate symbols before beginning execution of the function body
  147. proper.
  148.  
  149. xlargstkbase is the zero-near end of argument stack.
  150. xlargstktop is the zero-far end of argument stack.  Like xlstktop,
  151. xlargstktop is a static bounds pointer.
  152.  
  153. *xlsp ("sp"=="stack pointer") is the most recent item on the argument stack.
  154.  
  155. xlfp ("fp"=="frame pointer") is the base of the current stackframe.
  156.  
  157.  
  158.  
  159.   What is a context?
  160.   ------------------
  161.  
  162. An xlisp "context" is something like a checkpoint, recording a
  163. particular point buried in the execution history so that we can
  164. abort/return back to it.  Contexts are used to implement call/return,
  165. catch/throw, signals, gotos, and breaks.  xlcontext points to the
  166. chain of active contexts, the top one being the second-newest active
  167. context.  (The newest -- that is, current -- active context is
  168. implemented by the variables xlstack xlenv xlfenv xldenv xlcontext
  169. xlargv xlargc xlfp xlsp.)  Context records are written by
  170. xljump.c:xlbegin() and read by xljump.c:xljump().  Context records
  171. are C structures on the C program stack; They are not in the dynamic
  172. memory pool or on the lisp execution or argument stacks.
  173.  
  174. To create a context, the function defines a local CONTEXT structure,
  175. and then calls xlbegin, passing the address of the structure, the
  176. appropriate context flags (ored together, if more than one), and an
  177. expression which is the tag for return and throw contexts, and NIL
  178. for others. xlend is used to end the context. Within the context, a
  179. setjmp using the jump buffer in the context structure provides the
  180. mechanism to return to the function. 
  181.  
  182. The xljump function takes three arguments, the target context, a mask
  183. value (which is returned by the setjmp in the target context), and a
  184. return value. The return value is typically NIL, but holds the return
  185. value of RETURN and THROW functions for those operations. xljump
  186. unwinds the contexts until the target context is found, or an
  187. UNWIND-PROTECT context is found. In the latter case, xunwindprotect
  188. saves the unwind target so that unwinding can resume.
  189.  
  190. The implemented context flags are:
  191.  
  192. CF_GO -- used by TAGBODY, searched by GO (and the xlgo function).
  193.  
  194. CF_RETURN -- used by named blocks and functions, searched by RETURN.
  195.  
  196. CF_THROW -- used by CATCH, searched by THROW.
  197.  
  198. CF_ERROR -- used by ERRSET to mark context of an error handler.
  199.  
  200. CF_UNWIND -- used by UNWIND-PROTECT.
  201.  
  202. CF_TOPLEVEL -- used at the top-level loop.
  203.  
  204. CF_BRKLEVEL -- used in top level and break loops. Searched on
  205. uncaught (with CF_ERROR) errors.
  206.  
  207. CF_CLEANUP -- used in the top level and break loops. Searched when
  208. going back a break level. Note that this and CF_BRKLEVEL always appear
  209. together in contexts, but imply different functionality when signaled.
  210.  
  211. CF_CONTINUE -- used in break loops to cause continuation.
  212.  
  213. In most cases, the mask value passed to xljump is the context flag.
  214. For the break loop, for example, this allows dispatching based on the
  215. flag value of BRKLEVEL (which reenters the debug loop), CLEANUP
  216. (which returns to the previous break loop), or CONTINUE (which
  217. attempts to continue execution). Starting with xlisp2.1g, in the case
  218. of the TAGBODY context, the mask value is the index into the TAGBODY
  219. of the jump target.
  220.  
  221.   What is an environment?
  222.   -----------------------
  223.  
  224. An environment is basically a store of symbol-value pairs, used to
  225. resolve variable references by the lisp program.  xlisp maintains
  226. three environments, in the global variables xlenv, xlfenv and xldenv.
  227.  
  228. xlenv and xlfenv are linked-list stacks which are pushed when we
  229. enter a function and popped when we exit it.  We also switch
  230. xlenv+xlfenf environments entirely when we begin executing a new
  231. closure (user-fn written in lisp). The xlenv environment is enlarged
  232. during a LET function (or other special form with value bindings),
  233. while the xlfenv environment is enlarged with FLET/MACROLET/LABELS.
  234.  
  235. The xlenv environment is the most heavily used environment.  It is
  236. used to resolve everyday data references to local variables.  It
  237. consists of a list of frames (and objects).  Each frame is a list of
  238. sym-val pairs.  In the case of an object, we check all the instance
  239. and class variables of the object, then do the same for its
  240. superclass, until we run out of superclasses. If the symbol is not
  241. found it has not been bound and its global value is used.
  242.  
  243. The xlfenv environment is used to find function values instead of
  244. variable values. 
  245.  
  246. When we send a message, we set xlenv to the value it had when the
  247. message CLOSURE was built, then push on (obj msg-class), where
  248. msg-class is the [super]class defining the method.  (We also set
  249. xlfenv to the value xlfenv had when the method was built.)  This makes
  250. the object instance variables part of the environment, and saves the
  251. information needed to correctly resolve references to class variables,
  252. and to implement SEND-SUPER.
  253.  
  254. The xldenv environment tracks the old values of global variables
  255. which we have changed but intend to restore later to their original
  256. values, particularly for variables (symbols) marked as F_SPECIAL, but
  257. also for progv. This is also used internally when we bind and unbind
  258. s_evalhook and s_applyhook (*EVALHOOK* and *APPLYHOOK*). It is a
  259. simple list of sym-val pairs, treated as a stack.
  260.  
  261. These environments are manipulated in C via the xlisp.h macros
  262. xlframe(e), xlbind(s,v), xlfbind(s,v), xlpbind(s,v,e), xldbind(s,v),
  263. xlunbind(e).
  264.  
  265.  
  266.   How are multiple return values handled?
  267.   ---------------------------------------
  268.  
  269. The first value is always returned via the C function mechanism.
  270. All return values are passed back in an array xlresults[]. The
  271. number of return values are specified in xlnumresults. In order to
  272. avoid having to repeat the multiple value code in every function, a
  273. flag in the SUBR or FSUBR cell indicates if multiple values are used,
  274. and code in xleval.c mimics multiple value functions for single value
  275. functions. The function VALUES, defined in xvalues() allows closures
  276. to return multiple values.
  277.  
  278.  
  279.   How are xlisp entities stored and identified?
  280.   ---------------------------------------------
  281.  
  282. Conceptually, xlisp manages memory as a single array of fixed-size
  283. objects.  Keeping all objects the same size simplifies memory
  284. management enormously, since any object can be allocated anywhere, and
  285. complex compacting schemes aren't needed.  Every LVAL pointer points
  286. somewhere in this array.  Every xlisp object has the basic format
  287. (xldmem.h:typdef struct node)
  288.  
  289.  struct node {
  290.      char n_type;
  291.      char n_flags;
  292.      LVAL car;
  293.      LVAL cdr;
  294.  }
  295.  
  296. where n_type is one of:
  297.  
  298.  FREE     A node on the freelist.
  299.  SUBR     A function implemented in C. (Needs evalutated arguments.)
  300.  FSUBR    A special function implemented in C. (Needs unevaluated arguments).
  301.  CONS     A regular lisp cons cell.
  302.  FIXNUM   An integer.
  303.  FLONUM   A floating-point number.
  304.  STRING   A string.
  305.  STREAM   An input or output file.
  306.  CHAR      An ascii character.
  307.  USTREAM  An internal stream.
  308.  RATIO    A ratio.
  309.  SYMBOL   A symbol.
  310.  OBJECT   Any object, including class objects.
  311.  VECTOR      A variable-size array of LVALs.
  312.  CLOSURE  Result of DEFUN or LAMBDA -- a function written in lisp.
  313.  STRUCT      A structure.
  314.  COMPLEX  A complex number
  315.  PACKAGE  A package
  316.  
  317. Messages may be sent only to nodes with n_type == OBJECT.
  318.  
  319. Obviously, several of the above types won't fit in a fixed-size
  320. two-slot node.  The escape is to have them malloc() some memory and
  321. have one of the slots point to it -- VECTOR is the archetype.  For
  322. example, see xldmem.c:newvector().  To some extent, this malloc()
  323. hack simply exports the memory- fragmentation problem to the C
  324. malloc()/free() routines.  However, it helps keep xlisp simple, and
  325. it has the happy side-effect of unpinning the body of the vector, so
  326. that vectors can easily be expanded and contracted.  When the
  327. dldmem.c version of the memory manager is used, this memory is
  328. managed by xlisp.
  329.  
  330. The garbage collector has special-case code for each of the above node
  331. types, so it can find all LVAL slots and recycle any malloc()ed ram
  332. when a node is garbage-collected. If the collected node is a STREAM,
  333. then it's associated file is closed.
  334.  
  335. Xlisp pre-allocates nodes for all ascii characters, and for small
  336. integers.  These nodes are never garbage-collected.
  337.  
  338. As a practical matter, allocating all nodes in a single array is not
  339. very sensible.  Instead, nodes are allocated as needed, in segments of
  340. one or two thousand nodes, and the segments linked by a pointer chain
  341. rooted at xldmem.c:segs.
  342.  
  343.  
  344.  
  345.   How are vectors implemented?
  346.   ----------------------------
  347.  
  348. An xlisp vector is a generic array of LVAL slots.  Vectors are also
  349. the canonical illustration of xlisp's escape mechanism for node types
  350. which need more than two LVAL slots (the maximum possible in the
  351. fixed-size nodes in the dynamic memory pool).  The node CAR/CDR slots
  352. for a vector hold a size field plus a pointer to a malloc()ed ram
  353. chunk, which is automatically free()ed when the vector is
  354. garbage-collected.
  355.  
  356. xldmem.h defines macros for reading and writing vector fields and
  357. slots: getsize(), getelement() and setelement().  It also defines
  358. macros for accessing each of the other types of xlisp nodes.
  359.  
  360.  
  361.  
  362.   How are strings implemented?
  363.   ---------------------------- 
  364.  
  365. Strings work much like vectors: The node has a pointer to a malloc()ed
  366. ram chunk which is automatically free()ed when the string gets
  367. garbage-collected.
  368.  
  369.  
  370.  
  371.  How are symbols implemented?
  372.  ----------------------------
  373.  
  374. A symbol is a generic user-visible lisp variable, with separate slots
  375. for print name, value, function, and property list.  Any or all of
  376. these slots (including name) may be NIL.  You create a symbol in C by
  377. calling "xlmakesym(name)" or "xlenter(name)" (to make a symbol and
  378. enter it in the obarray). You create a symbol in xlisp with the READ
  379. function, or by calling "(gensym)", or indirectly in various ways.
  380. Most of the symbol-specific code in the interpreter is in xlsym.c.
  381.  
  382. Physically, a symbol is implemented like a four- or five-slot vector.
  383. A couple of free bits in the node structure are used as flags for
  384. F_SPECIAL (special variables) and F_CONSTANT (constants).
  385.  
  386. A symbol is marked as unbound (no value) or undefined (no function)
  387. by placing a pointer to symbol s_unbound in the value or function
  388. field, respectively. The symbol s_unbound is not interned and is not
  389. used other than to set and check for unbound variables and undefined
  390. functions.
  391.  
  392. The symbol NIL is statically allocated so its address is a constant.
  393. This makes the frequent comparision of a pointer to NIL faster and
  394. more compact (in some cases).
  395.  
  396. Random musing: Abstractly, the LISP symbols plus cons cells (etc)
  397. constitute a single directed graph, and the symbols mark spots where
  398. normal recursive evaluation should stop.  Normal lisp programming
  399. practice is to have a symbol in every cycle in the graph, so that
  400. recursive traversal can be done without MARK bits.
  401.  
  402.  
  403.  
  404.   How are closures implemented?
  405.   -----------------------------
  406.  
  407. A closure, the return value from a lambda, is a regular coded-in-lisp
  408. fn.  Physically, it it implemented like an eleven-slot vector, with the
  409. node n_type field hacked to contain CLOSURE instead of VECTOR. The
  410. vector slots contain:
  411.  
  412.  name   symbol -- 1st arg of DEFUN.  NIL for LAMBDA closures.
  413.  type   (s_lambda or s_macro).
  414.  args   List of "required" formal arguments (as symbols)
  415.  oargs  List of "optional" args, each like: (name (default specified-p))
  416.  rest   Name of "&rest" formal arg, else NIL.
  417.  kargs  keyword args, each like: ((':foo 'bar default specified-p))
  418.  aargs  &aux vars, each like: (('arg default))
  419.  body   actual code (as lisp list) for fn.
  420.  env    value of xlenv when the closure was built.  NIL for macros.
  421.  fenv   value of xlfenv when the closure was built. NIL for macros.
  422.  lambda The original formal args list in the defining form.
  423.  
  424. The lambda field is for printout purposes.  The remaining fields store
  425. a predigested version of the formal args list.  This is a limited form
  426. of compilation: by processing the args list at closure-creation time,
  427. we reduce the work needed during calls to the closure.
  428.  
  429.  
  430.  
  431.   How are objects implemented?
  432.   ----------------------------
  433.  
  434. An object is implemented like a vector, with the size determined by
  435. the number of instance variables.  The first slot in the vector points
  436. to the class of the object; the remaining slots hold the instance
  437. variables for the object.  An object needs enough slots to hold all
  438. the instance variables defined by its class, *plus* all the instance
  439. variables defined by all of its superclasses.
  440.  
  441.  
  442.  
  443.   How are classes implemented?
  444.   ----------------------------
  445.  
  446. A class is a specific kind of object, hence has a class pointer plus
  447. instance variables.  All classes have the following instance variables:
  448.  
  449.  MESSAGES   A list of (interned-symbol method-closure) pairs.
  450.  IVARS        Instance variable names: A list of interned symbols.
  451.  CVARS      Class variable names:    A list of interned symbols.
  452.  CVALS      Class variable values:   A vector of values.
  453.  SUPERCLASS A pointer to the superclass.
  454.  IVARCNT    Number of class instance variables, as a fixnum.
  455.  IVARTOTAL  Total number of instance variables, as a fixnum.
  456.  PNAME      Printname for this class
  457.  
  458. IVARCNT is the count of the number of instance variables defined by
  459. our class.  IVARTOTAL is the total number of instance variables in an
  460. object of this class -- IVARCNT for this class plus the IVARCNTs from
  461. all of our superclasses.
  462.  
  463.  
  464.  
  465.  
  466.   How is the class hierarchy laid out?
  467.   ------------------------------------
  468.  
  469. The fundamental objects are the OBJECT and CLASS class objects.  (Both
  470. are instances of class CLASS, and since CLASSes are a particular kind
  471. of OBJECT, both are also objects, with n_type==OBJECT.  Bear with me!)
  472.  
  473. OBJECT is the root of the class hierarchy: everything you can send a
  474. message to is of type OBJECT.  (Vectors, chars, integers and so forth
  475. stand outside the object hierarchy -- you can't send messages to them.
  476. I'm not sure why Dave did it this way.) OBJECT defines the messages:
  477.  
  478.  :isnew -- Does nothing
  479.  :class -- Returns contents of class-pointer slot.
  480.  :show  -- Prints names of obj, obj->class and instance vars (for debugging).
  481.  :prin1 -- print the object to argument stream
  482.  
  483. A CLASS is a specialized type of OBJECT (with instance variables like
  484. MESSAGES which generic OBJECTs lack), class CLASS hence has class
  485. OBJECT as its superclass.  The CLASS object defines the messages:
  486.  
  487.  :new     -- Create new object with self.IVARTOTAL LVAR slots, plus
  488.             one for the class pointer. Point class slot to self.
  489.             Set new.n_type char to OBJECT.
  490.  :isnew     -- Fill in IVARS, CVARS, CVALS, SUPERCLASS, IVARCNT and
  491.             IVARTOTAL, using parameters from :new call.  (The
  492.             :isnew msg inherits the :new msg parameters because
  493.             the  :isnew msg is generated automatically after
  494.             each :new   msg, courtesy of a special hack in
  495.             xlobj.c:sendmsg().)
  496.  :answer -- Add a (msg closure) pair to self.MESSAGES.
  497.  
  498.  
  499.  
  500. Here's a figure to summarize the above, with a generic object thrown
  501. in for good measure.  Note that all instances of CLASS will have a
  502. SUPERCLASS pointer, but no non-class object will.  Note also that the
  503. messages known to an object are those which can be reached by
  504. following exactly one Class Ptr and then zero or more Superclass Ptrs.
  505. For example, the generic object can respond to :ISNEW, :CLASS and
  506. :SHOW, but not to :NEW or :ANSWER.  (The functions implementing the
  507. given messages are shown in parentheses.)
  508.  
  509.                     NIL
  510.                      ^
  511.                      |
  512.                      |Superclass Ptr
  513.                      |
  514.                 Msg+--------+
  515.  :isnew (xlobj.c:obisnew) <----|  class |Class Ptr
  516.  :class (xlobj.c:obclass) <----| OBJECT |------------+
  517.  :show    (xlobj.c:objshow) <----|        |            |
  518.                    +--------+            |
  519.        +---------+                ^  ^               |
  520.        | generic |Class Ptr       |  |               |
  521.        | object  |----------------+  |Superclass Ptr |
  522.        +---------+             |               |
  523.                 Msg+--------+            |
  524.  :isnew    (xlobj.c:clnew)      <----| class  |Class Ptr   |
  525.  :new    (xlobj.c:clisnew) <----| CLASS  |--------+   |
  526.  :answer(xlobj.c:clanswer)<----|        |        |   |
  527.                    +--------+        |   |
  528.                   ^  ^           |   |
  529.                   |  |           |   |
  530.                   |  +-----------+   |
  531.                   +------------------+
  532.  
  533.  
  534. Thus, class CLASS inherits the :CLASS and :SHOW messages from class
  535. OBJECT, overrides the default :ISNEW message, and provides new the
  536. messages :NEW and :ANSWER.
  537.  
  538. New classes are created by (send CLASS :NEW ...) messages.  Their
  539. Class Ptr will point to CLASS.  By default, they will have OBJECT as
  540. their superclass, but this can be overridden by the second optional
  541. argument to :NEW.
  542.  
  543. The above basic structure is set up by xlobj.c:xloinit().
  544.  
  545.  
  546.  
  547.   How do we look up the value of a variable?
  548.   ------------------------------------------
  549.  
  550. When we're cruising along evaluating an expression and encounter a
  551. symbol, the symbol might refer to a global variable, an instance
  552. variable, or a class variable in any of our superclasses.  Figuring
  553. out which means digging throught the environment.  The canonical place
  554. this happens is in xleval.c:xleval(), which simply passes the buck to
  555. xlsym.c:xlgetvalue(), which in turn passes the buck to
  556. xlxsym.c:xlxgetvalue(), where the fun of scanning down xlenv begins.
  557. The xlenv environment looks something like
  558.  
  559.      Backbone    Environment frame contents
  560.      --------    --------------------------
  561. xlenv --> frame      ((sym val) (sym val) (sym val) ... )
  562.       frame      ...
  563.       object     (obj msg-class)
  564.       frame      ...
  565.       object     ...
  566.       frame      ...
  567.       ...
  568.  
  569. The "frame" lines are due to everyday nested constructs like LET
  570. expressions, while the "object" lines represent an object environment
  571. entered via a message send.  xlxgetvalue scans the enviroment left to
  572. right, and then top to bottom.  It scans down the regular environment
  573. frames itself, and calls xlobj.c:xlobjgetvalue() to search the object
  574. environment frames.
  575.  
  576. xlobjgetvalue() first searches for the symbol in the msg-class, then
  577. in all the successive superclasses of msg-class.  In each class, it
  578. first checks the list of instance-variable names in the IVARS slot,
  579. then the list of class-variables name in the CVARS slot.
  580.  
  581.   
  582.  
  583.   How are function calls implemented?
  584.   -----------------------------------
  585.  
  586. xleval.c contains the central expression-evaluation code.
  587. xleval.c:xleval() is the standard top-level entrypoint.  The two
  588. central functions are xleval.c:xlevform() and xleval.c:evfun().
  589. xlevform() can evaluate four kinds of expression nodes:
  590.  
  591. SUBR: A normal primitive fn coded in C.  We call evpushargs() to
  592. evaluate and push the arguments, then call the primitive.
  593.  
  594. FSUBR: A special primitive fn coded in C, which (like IF) wants its
  595. arguments unevaluated.  We call pushargs() (instead of evpushargs())
  596. and then the C fn.
  597.  
  598. CLOSURE: A preprocessed written-in-lisp fn from a DEFUN or LAMBDA.  We
  599. call evpushargs() and then evfun().
  600.  
  601. CONS: We issue an error if it isn't a LAMBDA, otherwise we call
  602. xleval.c:xlclose() to build a CLOSURE from the LAMBDA, and fall into
  603. the CLOSURE code.
  604.  
  605. The common thread in all the above cases is that we call evpushargs()
  606. or pushargs() to push all the arguments on the evaluation stack,
  607. leaving the number and location of the arguments in the global
  608. variables xlargc and xlargv.  The primitive C functions consume
  609. their arguments directly from the argument stack.
  610.  
  611. xleval.c:evfun() evaluates a CLOSURE by:
  612.  
  613. (1) Switching xlenv and xlfenv to the values they had when
  614. the CLOSURE was built. (These values are recorded in the CLOSURE.)
  615.  
  616. (2) Binding the arguments to the environment.  This involves scanning
  617. through the section of the argument stack indicated by xlargc/xlargv,
  618. using information from the CLOSURE to resolve keyword arguments
  619. correctly and assign appropriate default values to optional arguments,
  620. among other things.
  621.  
  622. (3) Evaluating the body of the function via xleval.c:xleval().
  623.  
  624. (4) Cleaning up and restoring the original environment.
  625.  
  626.  
  627.  
  628.   How are message-sends implemented?
  629.   ----------------------------------
  630.  
  631. We scan the MESSAGES list in the CLASS object of the recipient,
  632. looking for a (message-symbol method) pair that matches our message
  633. symbol.  If necessary, we scan the MESSAGES lists of the recipients
  634. superclasses too.  (xlobj.c:sendmsg().)  Once we find it, we basically
  635. do a normal function evaluation. (xlobjl.c:evmethod().)  Two oddities:
  636. We need to replace the message-symbol by the recipient on the argument
  637. stack to make things look normal, and we need to push an 'object'
  638. stack entry on the xlenv environment so we remember which class is
  639. handling the message.
  640.  
  641.  
  642.  
  643.   How is garbage collection implemented?
  644.   --------------------------------------
  645.  
  646. The dynamic memory pool managed by xlisp consists of a chain of memory
  647. *segments* rooted at global C variable "segs".  Each segment contains
  648. an array of "struct node"s plus a pointer to the next segment.  Each
  649. node contains a n_type field and a MARK bit, which is zero except
  650. during garbage collection.
  651.  
  652. Xlisp uses a simple, classical mark-and-sweep garbage collector.  When
  653. it runs out of memory (fnodes==NIL), it does a recursive traversal
  654. setting the MARK flag on all nodes reachable from the obarray, the
  655. three environments xlenv/xlfenv/xldenv, and the evaluation and
  656. argument stacks.  (A "switch" on the n_type field tells us how to find
  657. all the LVAL slots in the node (plus associated storage), and a
  658. pointer-reversal trick lets us avoid using too much stack space during
  659. the traversal.)  sweep() then adds all un-MARKed LVALs to fnodes, and
  660. clears the MARK bit on the remaining nodes.  If this fails to produce
  661. enough free nodes, a new segment is malloc()ed.
  662.  
  663. The code to do this stuff is mostly in xldmem.c.
  664.  
  665.  
  666. How is garbage collection of vectors/strings implemented?
  667. ---------------------------------------------------------
  668.  
  669. In the dldmem.c version, vector/string space is allocated from a
  670. memory pool maintained by xlisp, rather than relying on the C libary
  671. malloc() and free() functions. The pool is a linked list of VSEGMENT
  672. with the root vsegments.
  673.  
  674. When no free memory of a size necessary for an allocation request is
  675. available, a garbage collection is performed. If there is still no
  676. available memory, then a new vector segment is allocated.
  677.  
  678. The garbage collection process compacts the memory in each vector
  679. segment. This is an easy process because each allocated vector area
  680. is pointed to by only one location (in an LVAL), and a backpointer is
  681. maintained from the vector segment to the LVAL. Empty vector segments
  682. are returned to the heap using free() if there greater than 50% of
  683. the vector space is free. This is done to reduce thrashing while
  684. making memory available for allocation to LVALs.
  685.  
  686.  
  687.  How does XLISP initialize?
  688.  --------------------------
  689.  
  690. A major confusing aspect of xlisp is how it initializes, and how
  691. save/restore works. This is a multistep process that must take place
  692. in a specific order. 
  693.  
  694. When xlisp starts, there is no obarray, and in fact no symbols at
  695. all. All initial symbols must be created as part of initialization.
  696. In addition the character and small integer cells must be created,
  697. and all the C variables that point to xlisp symbols must be
  698. initialized. 
  699.  
  700. Initialization is mostly performed in xlinit.c, from the function
  701. xlinit(). This function is called from main() after main parses the
  702. command line, calls osinit() to initialize the OS interface, and sets
  703. the initial top level context. This initial context causes a fatal
  704. error to occur if any error happens during the initialization
  705. process. 
  706.  
  707. The first step of xlinit() is to call xlminit(), which is in xldmem.c
  708. or dldmem.c. This initializes the pointers for the memory manager,
  709. stacks, creates the small integer and character LVAL segments, and
  710. creates the NIL symbol by filling in the statically allocated NIL
  711. LVAL from one that is temporarily created. This first call of
  712. xlmakesym will do the first garbage collection -- all of the roots
  713. used for the mark routine have been set so that marking will not
  714. occur (there is nothing to mark!). It is important, however, that
  715. garbage collection not occur again until initialization is completed.
  716. This can be assured by having the allocation segment size, NNODES, be
  717. large enough for the entire initialization.
  718.  
  719. The second step in xlinit() is to call xldbug.c:xldinit() to turn
  720. debugging off.
  721.  
  722. At this point, if a restore is to occur from a workspace file, then
  723. the restore is attempted. If the restore is sucessful, then
  724. initialization is finished. See "How does save/restore work?" which
  725. is the next question.
  726.  
  727. If the restore fails or there is no file to restore, an initial
  728. workspace must be created. This is done by function initwks(), also
  729. in xlinit.c.
  730.  
  731. Initwks() starts by calling four initialization functions. 
  732.  
  733. xlsym.c:xlsinit() creates the symbol *OBARRAY* (and sets the
  734. variable obarray to point to it), creates the object array as the
  735. value, and enters obarray into the array. 
  736.  
  737. xlsymbols() is called next. It enters all of the symbols referenced
  738. by the interpreter into the obarray, and saves their addresses in C
  739. variables. This function is also called during restores, so it is
  740. important that it does not change the value of any symbols where the
  741. value would be set by restore. If the unbound indicator symbol does
  742. not exist, one is created. Then it puts NIL in the obarray if not
  743. already there (NIL being already created), then all the other symbols 
  744. are added (if they don't exist), and their addresses saved in C
  745. variables. 
  746.  
  747. This function also initializes constants such as NIL, T, and PI.
  748. Because a saved workspace might have a different file stream
  749. environment, xlsymbols always initializes the standard file streams
  750. based on the current xlisp invocation. It builds the structure
  751. property for RANDOM-STATE structures. It then (shudder!) calls two
  752. other initialization functions xlobj.c:obsymbols() and ossymbols (in
  753. the appropriate *stuff.c file) to enter symbols used in the object
  754. feature and operating system specific code, respectively.
  755.  
  756. Returning to initwks(), two aditional initialization routines are
  757. called. Xlread.c:xlrinit() initializes the read-table and installs
  758. the standard read macros. Xlobj.c:xloinit() creates the class and
  759. object objects.
  760.  
  761. Since the NIL and *OBARRAY* symbols were created before the unbound
  762. marker symbol, initwks sets the function values of these symbols, and
  763. of the unbound marker symbol, to be unbound. It then initializes all
  764. the global variables. Finally it creates all the builtin function
  765. bindings from the funtab table. The synonym functions are created
  766. last. 
  767.  
  768.  
  769.  How are workspaces saved and restored?
  770.  --------------------------------------
  771.  
  772. All the work is done in dlimage.c or xlimage.c, depending on the
  773. memory management scheme. The basic trick in a save is that memory
  774. locations upon a restore would be entirely different. Because of
  775. that, addresses written out are converted into offsets from the start
  776. of the segs LVAL segment list. In the restore operation, the offset
  777. is converted back into an address; if the offset is larger than the
  778. allocated segment memory, additional segment memory is allocated
  779. until an address can be calculated.
  780.  
  781. Looking at the save function, xlisave(char *fname), the argument
  782. string is taken as the name of a workspace file, and a binary file is
  783. created of that name. Then a garbage collection is performed since it
  784. would be wasteful to write out garbage.
  785.  
  786. The size of ftab is writen as a validity check, figuring that if the
  787. configuration changed, then this value would be different.
  788.  
  789. The offset of the *obarray* symbol is written next, since obarray is
  790. crucial to doing a restore -- it is used to get the addresses of all
  791. the other symbols used in the interpreter. Since NIL is a statically
  792. allocated symbol, the offsets to its function, property list, and
  793. printname are written. (I bet you didn't know you could define a
  794. function called NIL!)
  795.  
  796. Now the segs are traversed and all nodes are written out. The nodes
  797. are written in the format of a one byte tag followed by information
  798. dependent on the node type. Since many locations in the segment can
  799. be empty, one node type, FREE, has data that sets the next offset in
  800. the file, thus allowing skipping of many locations with a single
  801. command. The function setoffset(), called before writing tags in the
  802. other cases, handles writing the FREE entry. CONS and USTREAM cells
  803. consist of two pointers, which are written after conversion into
  804. offsets. For all other node types, the raw information is written by
  805. writenode(). This could be optimized since not all information is
  806. needed (for instance, the address of arrays won't be needed!)
  807.  
  808. A terminator entry (FREE with length of zero) is written, and the
  809. segs are traversed again, looking for nodes with attached
  810. array/string data and streams. For the types where the attached data
  811. is an array, the array elements (pointers) are converted to offsets
  812. and written out. For a string, the characters are written. For a
  813. stream (assuming FILETABLE is used), the file's name and position are
  814. written if it is other than standard input, output, or error. These
  815. streams cannot be saved or restored.
  816.  
  817.  
  818. Restoring a workspace is somewhat more difficult. The file is opened
  819. and checked for validity. Then the old memory image is deleted.
  820. During the deletion, any open file streams other than standard input,
  821. output, or error are closed. All of the global memory allocation
  822. pointers and stacks are reset, just like in initialization. Since the
  823. fixnum and character segments are of fixed size and need a known
  824. location, there are allocated. Their values, however, will be filled
  825. in from the workspace file. This is another wasteful inefficiency,
  826. but at least these segments are small.
  827.  
  828. The global pointer obarray is read from the file. As mentioned
  829. earlier, the cviptr() function converts the offset in the file into a
  830. physical address, allocating more node segments as necessary. The
  831. array portion of the NIL symbol is allocated, and its function,
  832. property list, and printname pointers are read from the file.
  833.  
  834. Then the node information is read in. For type FREE, the offset is
  835. adjusted. For CONS and USTREAM the car and cdr pointers are read, for
  836. other types, the LVAL data is read raw.
  837.  
  838.  
  839. The LVAL segments are scanned, and now the vector/string components
  840. of nodes are read. Since the order of nodes is unchanged, the data is
  841. read into the correct nodes. For vector types, the size field of the
  842. LVAL is consulted, vector space is allocated, and offsets are read
  843. from the file and converted into pointers. For strings, the string
  844. space is allocated and the string is read. For streams other than
  845. standard input, output, or error, the file name and position is read
  846. and an attempt is made to open the file. If the file can be opened,
  847. then it is positioned.
  848.  
  849. During the scan, for SUBR/FSUBR nodes funtab is consulted and the
  850. correct subroutine address is inserted.
  851.  
  852. During the whole process, if a tag is invalid or the file size is not
  853. correct, a fatal error occurs.
  854.  
  855. A garbage collection is performed to initialize the free space for
  856. future node allocation.
  857.  
  858. Finally xlsymbols() is called, as in the initializaton process, so
  859. that the C pointers in the interpreter can be set. This also sets the
  860. streams for standard input, output, and error to the correct values.
  861.  
  862.  
  863.  How do I add a new primitive fn to xlisp?
  864.  -----------------------------------------
  865.  
  866. Add a line to the end of xlftab.c:funtab[], and a function prototype
  867. to xlftab.h.  This table contains a list of triples:
  868.  
  869. The first element of each triple is the function name as it will
  870. appear to the programmer. Make it all upper case.
  871.  
  872. The second element is S (for SUBR) if (like most fns) your function
  873. wants its arguments pre-evaluated, else F (for FSUBR).
  874.  
  875. The third element is the name of the C function to call.
  876.  
  877. Remember that your arguments arrive on the xlisp argument rather
  878. than via the usual C parameter mechanism.
  879.  
  880. CAUTION: Try to keep your files separate from generic xlisp files, and
  881. to minimize the number of changes you make in the generic xlisp files.
  882. This way, you'll have an easier time re-installing your changes when
  883. new versions of xlisp come out.  It's a good idea to put a marker
  884. (like a comment with your initials) on each line you change or insert
  885. in the generic xlisp fileset.  For example, if you are going to add
  886. many primitive functions to your xlisp, use an #include file rather
  887. than putting them all in xlftab.c.
  888.  
  889. CAUTION: Remember that you usually need to protect the LVAL variables
  890. in your function from the garbage-collector.  It never hurts to do
  891. this, and often produces obscure bugs if you dont.  Generic code for
  892. a new primitive fn:
  893.  
  894. /* xlsamplefun - do useless stuff. */
  895. /* Called like (samplefun '(a c b) 1 2.0) */
  896. LVAL xlsamplefun()
  897. {
  898.     /* Variables to hold the arguments: */
  899.     LVAL    list_arg, integer_arg, float_arg;
  900.  
  901.     /* Get the arguments, with appropriate errors */
  902.     /* if any are of the wrong type.  Look in     */
  903.     /* xlisp.h for macros to read other types of  */
  904.     /* arguments.  Look in xlmath.c for examples  */
  905.     /* of functions which can handle an argument  */
  906.     /* which may be either int or float:          */
  907.     list_arg    = xlgalist()  ;  /* "XLisp Get A LIST"   */
  908.     integer_arg = xlgafixnum();  /* "XLisp Get A FIXNUM" */
  909.     float_arg   = xlgaflonum();  /* "XLisp Get A FLONUM" */
  910.  
  911.     /* Issue an error message if there are any extra arguments: */
  912.     xllastarg();
  913.  
  914.  
  915.  
  916.     /* Call a separate C function to do the actual  */
  917.     /* work.  This way, the main function can       */
  918.     /* be called from both xlisp code and C code.   */
  919.     /* By convention, the name of the xlisp wrapper */
  920.     /* starts with "xl", and the native C function  */
  921.     /* has the same name minus the "xl" prefix:     */
  922.     return samplefun( list_arg, integer_arg, float_arg );
  923. }
  924. LVAL samplefun( list_arg, integer_arg, float_arg )
  925. LVAL            list_arg, integer_arg, float_arg;
  926. {
  927.     FIXTYPE val_of_integer_arg;
  928.     FLOTYPE val_of_float_arg;
  929.  
  930.     /* Variables which will point to LISP objects: */
  931.     LVAL result;
  932.  
  933.     LVAL list_ptr;
  934.     LVAL float_ptr;
  935.     LVAL int_ptr;
  936.  
  937.     /* Protect our internal pointers by */
  938.     /* pushing them on the evaluation   */
  939.     /* stack so the garbage collector   */
  940.     /* can't recycle them in the middle */
  941.     /* of the routine:                  */
  942.     xlstkcheck(3);    /* Make sure following xlsave */
  943.                       /* calls won't overrun stack. */
  944.     xlsave(list_ptr); /* Use xlsave1() if you don't */
  945.     xlsave(float_ptr);/* do an xlstkcheck().        */
  946.     xlsave(int_ptr);
  947.  
  948.     /* Create an internal list structure, protected */
  949.     /* against garbage collection until we exit fn: */
  950.     list_ptr = cons(list_arg,list_arg);
  951.  
  952.     /* Get the actual values of our fixnum and flonum: */
  953.     val_of_integer_arg = getfixnum( integer_arg );
  954.     val_of_float_arg   = getflonum( float_arg   );
  955.  
  956.  
  957.     /*******************************************/
  958.     /* You can have any amount of intermediate */
  959.     /* computations at this point in the fn... */
  960.     /*******************************************/
  961.  
  962.  
  963.     /* Make new numeric values to return: */
  964.     integer_ptr = cvflonum( val_of_integer_arg * 3   );
  965.     float_ptr   = cvflonum( val_of_float_arg   * 3.0 );
  966.  
  967.     /* Cons it all together to produce a return value: */
  968.     result = cons(
  969.         list_ptr,
  970.         cons(
  971.             integer_ptr,
  972.             cons(
  973.                 float_ptr,
  974.                 NIL
  975.             )
  976.         )
  977.     );
  978.  
  979.     /* Restore the stack, cancelling the xlsave()s: */
  980.     xlpopn(3); /* Use xlpop() for a single argument.*/
  981.  
  982.     return result;
  983. }
  984.  
  985.  
  986. - Jeff   (S)
  987.  
  988.  
  989.